using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using Palaso.Reporting;
using SpellCenter.Core;

namespace SpellCenter.UI
{
    /// <summary>
    /// To use this class, instantiate an object then call in succession:
    /// Request(), WaitForFinish(), and GetRequestedResults().  This decoupling 
    /// allows the UI to use a waiting mechanism if necessary.
    /// </summary>
    class WordContextBackgroundWorker : IDisposable
    {
        private readonly Thread _thread;
        private readonly IList<SpellWordInfo> _spellWords;
        private readonly Queue<IList<WordContext>> _cache;
        private int _spellWordIndex;
        private int _pending;
        // changed by Main Thread
        private int _requestedPending;
        private State _state;
        private Font _font;
        private int _width;

        private const int QUEUE_SIZE = 5;


        /* We need the whole list of spell words so we can get enough to 
         * fill the queue
         */ 
        public WordContextBackgroundWorker(IList<SpellWordInfo> spellWords)
        {
            _spellWords = spellWords;
            _spellWordIndex = 0;
            _state = State.Idle;
            _cache = new Queue<IList<WordContext>>();
            _pending = 0;

            ThreadStart ts = Run;
            _thread = new Thread(ts);
            _thread.IsBackground = true;
            _thread.Name = "Word context background worker thread";
            _thread.Start();
        }

        #region Events

        public event EventHandler Waiting;
        private void OnWaiting(EventArgs e)
        {
            EventHandler handler = Waiting;
            if(handler != null)
            {
                Waiting(this, e);
            }
        }

        public event EventHandler DoneWaiting;
        private void OnDoneWaiting( EventArgs e)
        {
            
            EventHandler handler = DoneWaiting;
            if (handler != null)
            {
                DoneWaiting(this, e);
            }
        }

        #endregion

        private int GetPendingTail(int pending)
        {
            if (pending + QUEUE_SIZE <= _spellWords.Count)
            {
                return pending + QUEUE_SIZE;
            }
            else
            {
                return _spellWords.Count;
            }
        }

        #region Worker Thread

        private void Run()
        {
            try
            {
                // While the state is less severe than "on it's death bed"
                while (InternalState < State.Dying)
                {
                    if(InternalState == State.Idle)
                    {
                        Thread.Sleep(10); // Give up control
                    }
                    SpellWordInfo current;
                    int width = 0;
                    Font font = null;
                    if (NextJob(out current, ref width, ref font))
                    {
                        DoWork(current, width, font);
                    }
                }
            }
            finally
            {
                InternalState = State.Dead;
            }
        }

        private void DoWork(SpellWordInfo current, int width, Font font)
        {
            Debug.Assert(current != null);
            if(InternalState == State.Dying)
                return;
            List<WordContext> result = WordContextControlPM.GetContentByPixelWidth(current, width, font);
            
            lock(this)
            {
                _cache.Enqueue(result);
                _spellWordIndex++;
            }
        }

        private bool NextJob(out SpellWordInfo word, ref int width, ref Font font)
        {
            Monitor.Enter(this);
            if (HasWorkAvailable())
            {
                Debug.Assert(_cache.Count <= QUEUE_SIZE);
                InternalState = State.Working;
                Debug.Assert(_spellWordIndex >= 0 && _spellWordIndex < _spellWords.Count);
                word = _spellWords[_spellWordIndex];

                // before we release the lock, save copies of information we're going to need
                width = _width;
                font = (Font) _font.Clone();
                Monitor.Exit(this);
                Debug.Assert(word != null);
                return true;
            }
            else
            {
                InternalState = State.Idle;
                word = null;

                Monitor.Exit(this);
                return false;
            }
        }

        private bool HasWorkAvailable()
        {
            if(_state == State.Requesting)
            {
                RemoveUnwantedObjects();
                if (_spellWordIndex < GetPendingTail(_pending))
                    return true;
                else return false;
            }
            else if (_state == State.Working && _spellWordIndex < GetPendingTail(_pending))
                return true;
            return false;
        }

        private void RemoveUnwantedObjects()
        {
            if(_requestedPending >= _pending && _requestedPending < GetPendingTail(_pending))
            {
                for(int i=_pending; i<_requestedPending && i<_spellWordIndex; i++)
                {
                    _cache.Dequeue();
                }
                // _spellWordIndex shall remain where it is, and just fill out the rest of the cache
            }
            else
            {
                _cache.Clear();
                _spellWordIndex = _requestedPending;
            }
            _pending = _requestedPending;
        }

        #endregion

        #region Main Thread

        public void Request(int indexOfSpellWord, int width, Font font)
        {
            lock(this)
            {
                _width = width;
                _font = font;
                _requestedPending = indexOfSpellWord;
                InternalState = State.Requesting;
            }
        }

        public IList<WordContext> GetRequestedResults()
        {
            if (_cache.Count > 0)
            {
                IList<WordContext> peek = _cache.Peek();
                Monitor.Exit(this);
                if (InternalState == State.Dead)
                    // probably an exception in the worker thread
                    throw new InvalidOperationException("There was an error retrieving results");

                return peek;
            }
            else
            {
                Monitor.Exit(this); // In case someone catches this to recover
                throw new InvalidOperationException("No Jobs are ready, call WaitForFinish()");
            }
        }

        private bool IsRequestedAvailable()
        {
            Monitor.Enter(this);
            if(InternalState == State.Requesting)
            {
                Monitor.Exit(this);
                return false;
            }
            try
            {
                if(_cache.Count != 0)
                {
                    if(_spellWords[_pending].Word == _cache.Peek()[0].Word)
                    {
                        // Don't release the lock, it'll be released in GetRequestedResults()
                        return true;
                    }
                }
            } catch (IndexOutOfRangeException e)
            {
                // I release it here so that it's holding it for less time, finer grained control.
                Monitor.Exit(this);

                // something weird happened, requested job is out of order or something.
                // it's defitely not done though...
                Console.WriteLine("BackgroundWorker.IsRequestedAvailable(): IndexOutOfRangeException ocurred, recovering...");
                Logger.WriteEvent("IndexOutOfRangeException ocurred but was recovered: \n{0}", e.StackTrace);
                return false;
            }
            Monitor.Exit(this);
            return false;
        }
        public void WaitForFinish()
        {
            while(true)
            {
                if(IsRequestedAvailable())
                {
                    OnDoneWaiting(EventArgs.Empty);
                    break;
                }
                else
                {
                    OnWaiting(EventArgs.Empty);
                    Thread.Sleep(10);
                }
            }
        }

        #endregion

        #region State (inter-thread communication)

        private State InternalState
        {
            get
            {
                lock(_thread) return _state;
            }
            set
            {
                lock (_thread) //_state isn't an object, we don't need to block the other critical sections
                {
                    if(_state == State.Dead)
                        return; // Dead is the ultimate state, can't be raised from the dead...
                    
                    if(_state == State.Dying && value != State.Dead)
                        return; // We don't allow the worker to heal itself

                    _state = value;
                }
            }
        }

        private enum State
        {
            /// <summary> Worker is waiting for more work </summary>
            Idle,
            /// <summary> Main thread is requesting more work, worker will get to it when it feels like it </summary>
            Requesting,
            /// <summary> Worker thread is working </summary>
            Working,
            /// <summary> Main thread is asking for worker to Terminate </summary>
            Dying, // Only set while disposing, and it will never be called twice
            /// <summary> Worker has terminated </summary>
            Dead   // Set as Run() exits
        }

        #endregion

        #region IDisposable Members

        ///<summary>
        ///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        ///</summary>
        ///<filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        private void Dispose(bool disposing)
        {
            if(disposing)
            {
                _thread.Abort();//jh added this
//tim had this
                //InternalState = State.Dying;
//                while (InternalState != State.Dead)
//                    Thread.Sleep(10);
            }
        }
        ~WordContextBackgroundWorker()
        {
            Dispose(false);
            Debug.Fail("Object was not disposed");
        }

        #endregion
    }
}
